Моки в юнит-тестах
Моки это такие классы-заглушки (и соответсвенно объекты), которые позволяют избавиться от внешних зависимостей при модульном (unit) тестировании — ибо тестирование с зависимостями уже интеграционное или системное и требует больших ресурсов, состояния данных и как следствие - большей сложности.
С моками же можно тестировать контроллеры, где код с большей ответсвенностью, который вызывает тяжёлые модели, у которых вся тяжёлая логика веб-сервисов, баз данных, парсеров.. но которые уже покрыты тестами.
В PHPUnit моки встроены в PHPUnit_Framework_TestCase и легко получаются от метода getMock(класс, методы, ...). Сам по себе он не только даёт объект, но и определяет класс. Так что на случай использования статического метода, мок должен сработать.
$myMock = $this->getMock('MyParser', array('parseXLS'));
Число вызовов
Особенность моков, в отличие от простых заглушек (stub) в изменяющемся поведении. Поведение определяется правилами которые вы составите, например:
$myMock->expects($this->any())
Это мы получаем мок которому безразлично сколько раз он будет использоваться. Но можно быть указать строже — never,atLeastOnce,once,exactly(3)
Параметры и ожидания
Можно ограничить и параметры которые будут передаватьсяи ожидаемый результат
$myMock->method('parseXLS')
->with( $this->equalTo('fileAsParam.xls') )
->will($this->returnValue( 'text From XLS File' ))
Вместо чёткого равенства можно использовать и более гибкие greaterThan, stringContains, anything.
А вместо стандартного возвращения значения, можно ..
- onConsecutiveCalls( значения через запятую ) — в зависимости от порядка вызова метода будет возвращать переданные значения
- returnArgument(индекс переданного ранее аргумента)
- returnCallback(вызываемая лямбда)
- throwException( Эксепшн )
Проверка параметров
Для проверки используется ->with() с одним из встроенных методов:
$this->anything()
$this->contains($value)
$this->arrayHasKey($key)
$this->equalTo($value, $delta, $maxDepth)
$this->classHasAttribute($attribute)
$this->greaterThan($value)
$this->isInstanceOf($className)
$this->isType($type)
$this->matchesRegularExpression($pattern)
$this->stringContains($string, $case)
Проблемы
Я сталкивался с тем что фреймворк определяет класс и как следствие если в в дальнейшем инклудите реальный код, то получаете ошибку cannot redeclare class. Пока что это исправлял через использование пространства имён
Более критическая проблема с конструкторами - если вы используете parent::__construct() и при этом parent создан как мок, то можно получить Fatal Error: Cannot call constructor in ...